package com.ruoyi.yiqikan.service;

import com.alibaba.fastjson2.JSONObject;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class SocketIoService {
    @Autowired
    private SocketIOServer socketIOServer;
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    /**
     * key : token
     * value:socket
     */
    private static Map<String, SocketIOClient> userName_Socket = new ConcurrentHashMap<>();
    private static Map<UUID,String> sessionId_UserName = new ConcurrentHashMap<>();
    /**
     * 静态变量，用来记录当前在线连接数
     */
    private static AtomicInteger onlineNum = new AtomicInteger();
    /**
     * key:房主token
     * value:用户tokens
     */
    private static final ConcurrentHashMap<String, ArrayList<String>> room_Users = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, CircularFifoQueue<JSONObject>> room_Chats = new ConcurrentHashMap<>();
    /**
     * 房间配置文件
     * //      config:{
     *                 //        url:"urlurlurl",
     *                 //        type: "upDateRoomConfig",
     *                 //        token: loginService.token,
     *                 //        userName: loginService.loginBody.userName,
     *                 //        roomID: roomService.joinRoomID,
     *                 //        paused: false,
     *                 //        volume: 0,
     *                 //        duration: 0,
     *                 //        currentSrc: "",
     *                 //        currentTime: 0,
     *                 //        playbackRate:1,
     *                 //      },
     */
    private static final ConcurrentHashMap<String, JSONObject> room_Config = new ConcurrentHashMap<>();

    @Autowired
    private RedisCache redisCache;
    /**
     * 在线人数加一
     */
    public static void addOnlineCount() {
        onlineNum.incrementAndGet();
    }

    /**
     * 在线人数减一
     */
    public static void subOnlineCount() {
        onlineNum.decrementAndGet();
    }

    @Autowired
    private SysLoginService loginService;

    /**
     * Spring IoC容器创建之后，在加载SocketIOServiceImpl Bean之后启动
     *
     * @throws Exception
     */
    @PostConstruct
    private void autoStartup() throws Exception {
        start();
    }

    /**
     * Spring IoC容器在销毁SocketIOServiceImpl Bean之前关闭,避免重启项目服务端口占用问题
     *
     * @throws Exception
     */
    @PreDestroy
    private void autoStop() throws Exception {
        stop();
    }

    public void start() {
        // 监听客户端连接
        socketIOServer.addConnectListener(client -> {
            Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
//            if(SecurityUtils.getAuthentication().isAuthenticated()){
//                JSONObject jsonResult = new JSONObject();
//                jsonResult.put("msg", "没有权限");
//                client.sendEvent("ClientReceive", jsonResult);
//                return;
//            }
            if(!params.containsKey("uid")){
                // 过滤EIO请求
                // http://localhost:10246/socket.io/?EIO=3&transport=polling&t=O6PfciH&sid=5c8962bc-c5d4-4299-a655-ed578d44a6cb
                return;
            }
            String uid = params.get("uid").get(0);
            String userName = params.get("userName").get(0);
            String passWord = params.get("passWord").get(0);
            String code = params.get("code").get(0);
            String joinRoomID = params.get("joinRoomID").get(0);
            String token = params.get("token").get(0);
            if (uid != null) {
                //清除or刷新反复登录留下的脏token(前端未做websocket在background.js的持久化)
                if (StringUtils.isNotEmpty(token)) {
                    loginSuccess(client, userName, token,joinRoomID);
                } else {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("token", token);
                    jsonObject.put("type", "login");
                    jsonObject.put("msg", "未通过认证");
                    jsonObject.put("code", 0);
                    client.sendEvent("ClientReceive", jsonObject);
                }
                log.info("有新的客户端连接UID:{}", userName);
            }
        });

        // 监听客户端断开连接
        socketIOServer.addDisconnectListener(listener -> {
            disConnected(listener);
        });
        socketIOServer.addEventListener("ServerReceive", JSONObject.class, (client, data, ackSender) -> {
            log.info("接收到SID:{}发来的消息:{}", data.getString("userName"), data.toJSONString());
            handlerMessage(data);
        });

        socketIOServer.start();
        log.info("socket.io初始化服务完成");
    }

    private void disConnected(SocketIOClient listener) {
        UUID uuid=listener.getSessionId();
        if(sessionId_UserName.containsKey(uuid)){
            log.info("关闭SessionId:{}",uuid);
            String userName=sessionId_UserName.get(uuid);
            userName_Socket.remove(userName);
            String roomID=removeUserFromRoom(userName);
            if(StringUtils.isNotEmpty(roomID)){
                broadCastRoomUserNumber(roomID);
            }
            listener.disconnect();
            sessionId_UserName.remove(uuid);
            log.info(userName+"的连接中断");
        }
    }

    private String removeUserFromRoom(String userName) {
        String roomID=getUserRoomID(userName);
        if(StringUtils.isNotEmpty(roomID)&& room_Users.containsKey(roomID)){
            room_Users.get(roomID).remove(userName);
        }
        return roomID;
    }

    private String getUserRoomID(String userName) {
        Enumeration<String> enumeration= room_Users.keys();
        while(enumeration.hasMoreElements()){
            String key=enumeration.nextElement();
            if(room_Users.get(key).contains(userName)){
                return key;
            }
        }
        return null;
    }

    /**
     *
     * @param client
     * @param userName
     * @param token
     * @param joinRoomID 缓存在客户端.只要有创建或加入房间,则必有此ID
     */
    private void loginSuccess(SocketIOClient client, String userName, String token,String joinRoomID) {
        if (userName_Socket.containsKey(userName)) {
            userName_Socket.remove(userName).disconnect();
        }
        userName_Socket.put(userName, client);
        // 给客户端发送一条信息 发送ClientReceive事件 需要客户端绑定此事件即可接收到消息
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("token", token);
        jsonResult.put("type", "login");
        jsonResult.put("msg", "登录成功");
        jsonResult.put("code", 1);
        sessionId_UserName.put(client.getSessionId(),userName);
        log.info("sessionId:{}", client.getSessionId());
        //登录后,如果用户在房主名单(断线退出,不会删除房间,所以房间还在),则返回roomID
        if(room_Users.containsKey(userName)){
            jsonResult.put("roomID", userName);
            jsonResult.put("joinRoomID", userName);
        }
        if(StringUtils.isNotEmpty(joinRoomID)){
            jsonResult.put("config", room_Config.get(joinRoomID));
            if(room_Users.containsKey(joinRoomID)){
                room_Users.get(joinRoomID).add(userName);
            }
            jsonResult.put("joinRoomID", joinRoomID);
            broadCastRoomUserNumber(joinRoomID);
        }
        client.sendEvent("ClientReceive", jsonResult);
    }
    private void broadCastRoomUserNumber(String roomID){
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("type", "broadCastRoomUserNumber");
        if(!room_Users.containsKey(roomID)|| room_Users.get(roomID)==null){
            jsonResult.put("userNumber", 0);
        }else{
            jsonResult.put("userNumber", room_Users.get(roomID).size());
        }
        sendMessageToRoom(roomID,jsonResult);
    }
    public static void main(String[] args) {
        testChatQueue();
    }
    private static void testChatQueue(){
        CircularFifoQueue<String> chatQueue = new CircularFifoQueue<>(20);
        for(int i=0;i<20;i++){
            chatQueue.offer(i+"");
        }
        for(int i=0;i<chatQueue.size();i++){
            System.out.println(chatQueue.get(i));
        }
    }
    private void handlerMessage(JSONObject json) {
        String token = json.getString("token");
        String userName = json.getString("userName");
        String roomID = json.getString("roomID");
        String joinRoomID = json.getString("joinRoomID");
        if ("getToken".equals(json.getString("type"))) {
            JSONObject jsonResult = new JSONObject();
            jsonResult.put("type", "getToken");
            jsonResult.put("token", token);
            sendMessage(userName, jsonResult);
        } else if ("createRoom".equals(json.getString("type"))) {
            createRoom(json, userName);
        } else if ("getRoomConfig".equals(json.getString("type"))) {
            getRoomConfig(userName, roomID);
        } else if ("setRoomConfig".equals(json.getString("type"))) {
            setRoomConfig(json, userName);
        }else if ("destroyRoom".equals(json.getString("type"))) {
            destroyRoom(userName);
        } else if ("joinRoom".equals(json.getString("type"))) {
            joinRoom(userName, roomID);
        } else if ("upDateRoomConfig".equals(json.getString("type"))) {//TODO
            upDateRoomConfig(json, userName, roomID);
        } else if ("loginOut".equals(json.getString("type"))) {
            exitRoom(roomID,userName);
        } else if ("exitRoom".equals(json.getString("type"))) {
            exitRoom(roomID,userName);
        } else if ("chat".equals(json.getString("type"))) {
            chat(json, userName, roomID);
        }
    }

    private void chat(JSONObject json, String userName, String roomID) {
        if (roomID == null || !room_Users.containsKey(roomID)) {
            json.put("code", 0);
        } else {
            json.put("code", 1);

            //生成一条聊天记录
            JSONObject oneChat=new JSONObject();
            JSONObject chatMsg=json.getJSONObject("chatMsg");
            oneChat.put("msg", chatMsg.getString("msg"));
            oneChat.put("time", chatMsg.getString("time"));
            oneChat.put("userName", userName);
            room_Chats.get(roomID).offer(oneChat);
            json.put("chatList", room_Chats.get(roomID));
            sendMessageToRoom(roomID,json);
        }
    }

    public void destroyRoom(String userName) {
        ArrayList<String> users = room_Users.remove(userName);
        if (users == null||users.isEmpty()) return;
        //通知所有用户，关闭房间
        for (String oneUserName : users) {
            JSONObject jsonResult = new JSONObject();
            jsonResult.put("type", "destroyRoomSuccess");
            sendMessage(userName, jsonResult);

            //断开所有用户的连接
            userName_Socket.get(oneUserName).disconnect();
        }
    }

    private void upDateRoomConfig(JSONObject json, String userName, String roomID) {
        JSONObject jsonResult = json;
        if (StringUtils.isEmpty(roomID) || !room_Users.containsKey(roomID)) {
            jsonResult.put("code", 0);
        } else {
            jsonResult.put("code", 1);
            for (String roomUser : room_Users.get(roomID)) {
                //控制房间广播时,不广播给自己
                if (roomUser.equals(userName)) {
                    continue;
                }
                if(userName_Socket.containsKey(roomUser)){
                    userName_Socket.get(roomUser).sendEvent("ClientReceive", jsonResult);
                }
            }
        }
    }

    private void createRoom(JSONObject json, String userName) {
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("type", "createRoom");
        if (room_Users.containsKey(userName)) {
            jsonResult.put("code", 0);
            jsonResult.put("msg", "房间已存在");
        } else {
            ArrayList<String> oneRoom = new ArrayList<>();
            oneRoom.add(userName);//创建房间时,把自己放入自己房间中.
            room_Users.put(userName, oneRoom);

            JSONObject roomConfig= json.getJSONObject("config");
            room_Config.put(userName,roomConfig);
            jsonResult.put("code", 1);

            //创建房间聊天队列
            CircularFifoQueue<JSONObject> chatQueue = new CircularFifoQueue<>(20);
            room_Chats.put(userName,chatQueue);
        }
        sendMessage(userName, jsonResult);
        broadCastRoomUserNumber(userName);
    }

    private void joinRoom(String userName, String roomID) {
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("type", "joinRoom");
        //看看有没有这个房间
        if (StringUtils.isEmpty(roomID) || !room_Users.containsKey(roomID)) {
            jsonResult.put("code", -1);
            jsonResult.put("msg", "无此房间");
            sendMessage(userName, jsonResult);
            return;
        }
        //有就把当前用户加入房间
        room_Users.get(roomID).remove(userName);
        room_Users.get(roomID).add(userName);
        jsonResult.put("code", 1);
        jsonResult.put("roomID", roomID);
        //读取当前房间配置
        jsonResult.put("config", room_Config.get(roomID));
        sendMessage(userName, jsonResult);
        //登录成功后,通知房间内所有人
        broadCastRoomUserNumber(roomID);
    }

    private void setRoomConfig(JSONObject json, String userName) {
        JSONObject roomConfig= json.getJSONObject("config");
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("type", "setRoomConfig");
        String roomID=json.getString("roomID");
        if(StringUtils.isEmpty(roomID)){
            jsonResult.put("code", 0);
            jsonResult.put("msg", "房间不存在");
            sendMessage(userName, jsonResult);
            return;
        }else {
            room_Config.put(roomID,roomConfig);
            jsonResult.put("code", 1);
            jsonResult.put("config", roomConfig);
            for(String oneUserNameInRoom: room_Users.get(roomID)){
                if(userName.equals(oneUserNameInRoom)){
                    continue;
                }
                sendMessage(oneUserNameInRoom, jsonResult);
            }
        }
    }
    private void sendMessageToRoom(String roomID,JSONObject jsonResult){
        if(StringUtils.isEmpty(roomID)||room_Users.size()<1){
            return;
        }
        for(String oneUserNameInRoom: room_Users.get(roomID)){
            if(StringUtils.isNotEmpty(oneUserNameInRoom)){
                sendMessage(oneUserNameInRoom, jsonResult);
            }
        }
    }

    /**
     *
     *增加服务器端的room信息,一般配合setRoomConfig使用
     * @param roomID
     */
    private void addServerRoomConfig(String roomID) {
        JSONObject roomConfig= room_Config.get(roomID);
        roomConfig.put("userNumber", room_Users.get(roomID).size());
    }
    private void getRoomConfig(String userName, String roomID) {
        JSONObject jsonResult = new JSONObject();
        jsonResult.put("type", "getRoomConfig");
        if (!room_Users.containsKey(roomID)) {
            jsonResult.put("code", 0);
            jsonResult.put("msg", "房间不存在");
        } else {
            jsonResult.put("code", 1);
            jsonResult.put("config", room_Config.get(roomID));
        }
        sendMessage(userName, jsonResult);
    }
    public void exitRoom(String roomID,String userName){
        //如果发现是房主，则从房间列表移除房主房间，并销毁房间。
        if (StringUtils.isNotEmpty(roomID) && room_Users.containsKey(roomID)) {
            destroyRoom(userName);
        }
        //如果在观影者名单,则返回joinID及房间配置
        Enumeration<String> enumeration= room_Users.keys();
        while(enumeration.hasMoreElements()){
            String key=enumeration.nextElement();
            ArrayList userList= room_Users.get(key);
            if(userList.contains(userName)){
                userList.remove(userName);
            }
            if (userName_Socket.containsKey(userName)) {
                userName_Socket.remove(userName).disconnect();
            }
        }
        broadCastRoomUserNumber(roomID);
    }
    private void sendMessage(String userName, JSONObject jsonResult) {
        if(!userName_Socket.containsKey(userName)){
            log.info("发送消息时,未找到用户:"+userName+".消息体:"+jsonResult);
            return;
        }
        log.info("已发送消息:"+userName+"===="+jsonResult);
        userName_Socket.get(userName).sendEvent("ClientReceive", jsonResult);
    }

    public void stop() {
        if (socketIOServer != null) {
            socketIOServer.stop();
            socketIOServer = null;
        }
        log.info("socket.io服务已关闭");
    }

    /**
     * 此方法为获取client连接中的参数，可根据需求更改
     *
     * @param client
     * @return
     */
    private String getParamsByClient(SocketIOClient client) {
        // 从请求的连接中拿出参数（这里的sid必须是唯一标识）
        Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
        List<String> list = params.get("userName");
         if (list != null && list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    /**
     * 获取连接的客户端ip地址
     *
     * @param client: 客户端
     * @return: java.lang.String
     */
    private String getIpByClient(SocketIOClient client) {
        String sa = client.getRemoteAddress().toString();
        return sa.substring(1, sa.indexOf(":"));
    }
}
